#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2014                                                    *
#*   Yorik van Havre <yorik@uncreated.net>                                 *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

__title__ =  "FreeCAD IFC importer - Enhanced ifcopenshell-only version"
__author__ = "Yorik van Havre","Jonathan Wiedemann"
__url__ =    "http://www.freecadweb.org"

import os,time,tempfile,uuid,FreeCAD,Part,Draft,Arch,math,DraftVecUtils

DEBUG = False

if open.__module__ == '__builtin__':
    pyopen = open # because we'll redefine open below

# which IFC type must create which FreeCAD type
typesmap = { "Site":       ["IfcSite"],
             "Building":   ["IfcBuilding"],
             "Floor":      ["IfcBuildingStorey"],
             "Structure":  ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"],
             "Wall":       ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"],
             "Window":     ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"],
             "Roof":       ["IfcRoof"],
             "Stairs":     ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"],
             "Space":      ["IfcSpace"],
             "Rebar":      ["IfcReinforcingBar"],
             "Panel":      ["IfcPlate"],
             "Equipment":  ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"]
           }

# which IFC entity (product) is a structural object
structuralifcobjects = (
                       "IfcStructuralCurveMember", "IfcStructuralSurfaceMember",
                       "IfcStructuralPointConnection", "IfcStructuralCurveConnection", "IfcStructuralSurfaceConnection",
                       "IfcStructuralAction", "IfcStructuralPointAction",
                       "IfcStructuralLinearAction", "IfcStructuralLinearActionVarying", "IfcStructuralPlanarAction"
                       )

# specific name translations
translationtable = { "Foundation":"Footing",
                     "Floor":"BuildingStorey",
                     "Rebar":"ReinforcingBar",
                     "HydroEquipment":"SanitaryTerminal",
                     "ElectricEquipment":"ElectricAppliance",
                     "Furniture":"FurnishingElement",
                     "Stair Flight":"StairFlight",
                     "Curtain Wall":"CurtainWall"
                   }

ifctemplate = """ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('$filename','$timestamp',('$owner','$email'),('$company'),'IfcOpenShell','IfcOpenShell','');
FILE_SCHEMA(('$ifcschema'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'$owner',$,$,$,$,$);
#2=IFCORGANIZATION($,'$company',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'$version','FreeCAD','118df2cf_ed21_438e_a41');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,$now);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.,0.));
#11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10);
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCPROJECT('$projectid',#5,'$project',$,$,$,$,(#11),#19);
ENDSEC;
END-ISO-10303-21;
"""


def decode(filename,utf=False):
    if isinstance(filename,unicode):
        # workaround since ifcopenshell currently can't handle unicode filenames
        if utf:
            encoding = "utf8"
        else:
            import sys
            encoding = sys.getfilesystemencoding()
        filename = filename.encode(encoding)
    return filename


def doubleClickTree(item,column):
    txt = item.text(column)
    if "Entity #" in txt:
        eid = txt.split("#")[1].split(":")[0]
        addr = tree.findItems(eid,0,0)
        if addr:
            tree.scrollToItem(addr[0])
            addr[0].setSelected(True)


def getPreferences():
    """retrieves IFC preferences"""
    global DEBUG, PREFIX_NUMBERS, SKIP, SEPARATE_OPENINGS
    global ROOT_ELEMENT, GET_EXTRUSIONS, MERGE_MATERIALS
    global MERGE_MODE_ARCH, MERGE_MODE_STRUCT, CREATE_CLONES
    global FORCE_BREP, IMPORT_PROPERTIES, STORE_UID, SERIALIZE
    p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
    if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False):
        import FreeCADGui
        FreeCADGui.showPreferences("Import-Export",0)
    DEBUG = p.GetBool("ifcDebug",False)
    PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False)
    SKIP = p.GetString("ifcSkip","").split(",")
    SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False)
    ROOT_ELEMENT = p.GetString("ifcRootElement","IfcProduct")
    GET_EXTRUSIONS = p.GetBool("ifcGetExtrusions",False)
    MERGE_MATERIALS = p.GetBool("ifcMergeMaterials",False)
    MERGE_MODE_ARCH = p.GetInt("ifcImportModeArch",0)
    MERGE_MODE_STRUCT = p.GetInt("ifcImportModeStruct",1)
    if MERGE_MODE_ARCH > 0:
        SEPARATE_OPENINGS = False
        GET_EXTRUSIONS = False
    if not SEPARATE_OPENINGS:
        SKIP.append("IfcOpeningElement")
    CREATE_CLONES = p.GetBool("ifcCreateClones",True)
    FORCE_BREP = p.GetBool("ifcExportAsBrep",False)
    IMPORT_PROPERTIES = p.GetBool("ifcImportProperties",False)
    STORE_UID = p.GetBool("ifcStoreUid",True)
    SERIALIZE = p.GetBool("ifcSerialize",False)


def explore(filename=None):
    """explore([filename]): opens a dialog showing
    the contents of an IFC file. If no filename is given, a dialog will
    pop up to choose a file."""

    getPreferences()

    try:
        import ifcopenshell
    except:
        FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
        return

    if not filename:
        from PySide import QtGui
        filename = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.activeWindow(),'IFC files','*.ifc')
        if filename:
            filename = filename[0]

    from PySide import QtCore,QtGui

    filename = decode(filename,utf=True)

    if not os.path.exists(filename):
        print "File not found"
        return

    ifc = ifcopenshell.open(filename)
    global tree
    tree = QtGui.QTreeWidget()
    tree.setColumnCount(3)
    tree.setWordWrap(True)
    tree.header().setDefaultSectionSize(60)
    tree.header().resizeSection(0,60)
    tree.header().resizeSection(1,30)
    tree.header().setStretchLastSection(True)
    tree.headerItem().setText(0, "ID")
    tree.headerItem().setText(1, "")
    tree.headerItem().setText(2, "Item and Properties")
    bold = QtGui.QFont()
    bold.setWeight(75)
    bold.setBold(True)

    entities =  ifc.by_type("IfcRoot")
    entities += ifc.by_type("IfcRepresentation")
    entities += ifc.by_type("IfcRepresentationItem")
    entities += ifc.by_type("IfcPlacement")
    entities += ifc.by_type("IfcProperty")
    entities += ifc.by_type("IfcPhysicalSimpleQuantity")
    entities += ifc.by_type("IfcMaterial")
    entities += ifc.by_type("IfcProductRepresentation")
    entities = sorted(entities, key=lambda eid: eid.id())

    done = []

    for entity in entities:
        if hasattr(entity,"id"):
            if entity.id() in done:
                continue
            done.append(entity.id())
            item = QtGui.QTreeWidgetItem(tree)
            item.setText(0,str(entity.id()))
            if entity.is_a() in ["IfcWall","IfcWallStandardCase"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Wall_Tree.svg"))
            elif entity.is_a() in ["IfcBuildingElementProxy"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Component.svg"))
            elif entity.is_a() in ["IfcColumn","IfcColumnStandardCase","IfcBeam","IfcBeamStandardCase","IfcSlab","IfcFooting","IfcPile","IfcTendon"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Structure_Tree.svg"))
            elif entity.is_a() in ["IfcSite"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Site_Tree.svg"))
            elif entity.is_a() in ["IfcBuilding"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Building_Tree.svg"))
            elif entity.is_a() in ["IfcBuildingStorey"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Floor_Tree.svg"))
            elif entity.is_a() in ["IfcWindow","IfcWindowStandardCase","IfcDoor","IfcDoorStandardCase"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Window_Tree.svg"))
            elif entity.is_a() in ["IfcRoof"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Roof_Tree.svg"))
            elif entity.is_a() in ["IfcExtrudedAreaSolid","IfcClosedShell"]:
                item.setIcon(1,QtGui.QIcon(":icons/Tree_Part.svg"))
            elif entity.is_a() in ["IfcFace"]:
                item.setIcon(1,QtGui.QIcon(":icons/Draft_SwitchMode.svg"))
            elif entity.is_a() in ["IfcArbitraryClosedProfileDef","IfcPolyloop"]:
                item.setIcon(1,QtGui.QIcon(":icons/Draft_Draft.svg"))
            elif entity.is_a() in ["IfcPropertySingleValue","IfcQuantityArea","IfcQuantityVolume"]:
                item.setIcon(1,QtGui.QIcon(":icons/Tree_Annotation.svg"))
            elif entity.is_a() in ["IfcMaterial"]:
                item.setIcon(1,QtGui.QIcon(":icons/Arch_Material.svg"))
            item.setText(2,str(entity.is_a()))
            item.setFont(2,bold);

            i = 0
            while True:
                try:
                    argname = entity.attribute_name(i)
                except:
                    break
                else:
                    try:
                        argvalue = getattr(entity,argname)
                    except:
                        print "Error in entity ",entity
                        break
                    else:
                        if not argname in ["Id", "GlobalId"]:
                            colored = False
                            if isinstance(argvalue,ifcopenshell.entity_instance):
                                if argvalue.id() == 0:
                                    t = str(argvalue)
                                else:
                                    colored = True
                                    t = "Entity #" + str(argvalue.id()) + ": " + str(argvalue.is_a())
                            elif isinstance(argvalue,list):
                                t = ""
                            elif isinstance(argvalue,str) or isinstance(argvalue,unicode):
                                t = argvalue.encode("latin1")
                            else:
                                t = str(argvalue)
                            t = "    " + str(argname.encode("utf8")) + " : " + str(t)
                            item = QtGui.QTreeWidgetItem(tree)
                            item.setText(2,str(t))
                            if colored:
                                item.setForeground(2,QtGui.QBrush(QtGui.QColor("#005AFF")))
                            if isinstance(argvalue,list):
                                for argitem in argvalue:
                                    colored = False
                                    if isinstance(argitem,ifcopenshell.entity_instance):
                                        if argitem.id() == 0:
                                            t = str(argitem)
                                        else:
                                            colored = True
                                            t = "Entity #" + str(argitem.id()) + ": " + str(argitem.is_a())
                                    else:
                                        t = argitem
                                    t = "        " + str(t)
                                    item = QtGui.QTreeWidgetItem(tree)
                                    item.setText(2,str(t))
                                    if colored:
                                        item.setForeground(2,QtGui.QBrush(QtGui.QColor("#005AFF")))
                    i += 1

    d = QtGui.QDialog()
    d.setObjectName("IfcExplorer")
    d.setWindowTitle("Ifc Explorer")
    d.resize(640, 480)
    layout = QtGui.QVBoxLayout(d)
    layout.addWidget(tree)

    tree.itemDoubleClicked.connect(doubleClickTree)

    d.exec_()
    del tree
    return


def open(filename,skip=[],only=[],root=None):
    "opens an IFC file in a new document"

    docname = os.path.splitext(os.path.basename(filename))[0]
    docname = decode(docname,utf=True)
    doc = FreeCAD.newDocument(docname)
    doc.Label = docname
    doc = insert(filename,doc.Name,skip,only,root)
    return doc


def insert(filename,docname,skip=[],only=[],root=None):
    """insert(filename,docname,skip=[],only=[],root=None): imports the contents of an IFC file.
    skip can contain a list of ids of objects to be skipped, only can restrict the import to
    certain object ids (will also get their children) and root can be used to
    import only the derivates of a certain element type (default = ifcProduct)."""

    getPreferences()

    try:
        import ifcopenshell
    except:
        FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
        return

    if DEBUG: print "Opening ",filename,"...",
    try:
        doc = FreeCAD.getDocument(docname)
    except:
        doc = FreeCAD.newDocument(docname)
    FreeCAD.ActiveDocument = doc

    if DEBUG: print "done."

    global ROOT_ELEMENT
    if root:
        ROOT_ELEMENT = root

    #global ifcfile # keeping global for debugging purposes
    filename = decode(filename,utf=True)
    ifcfile = ifcopenshell.open(filename)
    from ifcopenshell import geom
    settings = ifcopenshell.geom.settings()
    settings.set(settings.USE_BREP_DATA,True)
    settings.set(settings.SEW_SHELLS,True)
    settings.set(settings.USE_WORLD_COORDS,True)
    if SEPARATE_OPENINGS:
        settings.set(settings.DISABLE_OPENING_SUBTRACTIONS,True)
    sites = ifcfile.by_type("IfcSite")
    buildings = ifcfile.by_type("IfcBuilding")
    floors = ifcfile.by_type("IfcBuildingStorey")
    products = ifcfile.by_type(ROOT_ELEMENT)
    openings = ifcfile.by_type("IfcOpeningElement")
    annotations = ifcfile.by_type("IfcAnnotation")
    materials = ifcfile.by_type("IfcMaterial")

    if DEBUG: print "Building relationships table...",

    # building relations tables
    objects = {} # { id:object, ... }
    additions = {} # { host:[child,...], ... }
    groups = {} # { host:[child,...], ... }     # used in structural IFC
    subtractions = [] # [ [opening,host], ... ]
    properties = {} # { obj : { cat : [property, ... ], ... }, ... }
    colors = {} # { id:(r,g,b) }
    shapes = {} # { id:shaoe } only used for merge mode
    structshapes = {} # { id:shaoe } only used for merge mode
    mattable = {} # { objid:matid }
    sharedobjects = {} # { representationmapid:object }
    for r in ifcfile.by_type("IfcRelContainedInSpatialStructure"):
        additions.setdefault(r.RelatingStructure.id(),[]).extend([e.id() for e in r.RelatedElements])
    for r in ifcfile.by_type("IfcRelAggregates"):
        additions.setdefault(r.RelatingObject.id(),[]).extend([e.id() for e in r.RelatedObjects])
    for r in ifcfile.by_type("IfcRelAssignsToGroup"):
        groups.setdefault(r.RelatingGroup.id(),[]).extend([e.id() for e in r.RelatedObjects])
    for r in ifcfile.by_type("IfcRelVoidsElement"):
        subtractions.append([r.RelatedOpeningElement.id(), r.RelatingBuildingElement.id()])
    for r in ifcfile.by_type("IfcRelDefinesByProperties"):
        for obj in r.RelatedObjects:
            if not obj.id() in properties :
                properties[obj.id()] = {}
            prop_by_category = {}
            prop = []
            if r.RelatingPropertyDefinition.is_a("IfcPropertySet"):
                prop.extend([e.id() for e in r.RelatingPropertyDefinition.HasProperties])
                prop_by_category[r.RelatingPropertyDefinition.id()] = prop
                properties[obj.id()].update(prop_by_category)
    for r in ifcfile.by_type("IfcRelAssociatesMaterial"):
        for o in r.RelatedObjects:
            mattable[o.id()] = r.RelatingMaterial.id()
    for r in ifcfile.by_type("IfcStyledItem"):
        if r.Styles:
            if r.Styles[0].is_a("IfcPresentationStyleAssignment"):
                if r.Styles[0].Styles[0].is_a("IfcSurfaceStyle"):
                    if r.Styles[0].Styles[0].Styles[0].is_a("IfcSurfaceStyleRendering"):
                        if r.Styles[0].Styles[0].Styles[0].SurfaceColour:
                            c = r.Styles[0].Styles[0].Styles[0].SurfaceColour
                            if r.Item:
                                for p in ifcfile.by_type("IfcProduct"):
                                    if hasattr(p,"Representation"):
                                        if p.Representation:
                                            for it in p.Representation.Representations:
                                                if it.Items:
                                                    if it.Items[0].id() == r.Item.id():
                                                        colors[p.id()] = (c.Red,c.Green,c.Blue)
                                                    elif it.Items[0].is_a("IfcBooleanResult"):
                                                        if (it.Items[0].FirstOperand.id() == r.Item.id()):
                                                            colors[p.id()] = (c.Red,c.Green,c.Blue)
                        else:
                            for m in ifcfile.by_type("IfcMaterialDefinitionRepresentation"):
                                for it in m.Representations:
                                    if it.Items:
                                        if it.Items[0].id() == r.id():
                                            colors[m.RepresentedMaterial.id()] = (c.Red,c.Green,c.Blue)

    if only: # only import a list of IDs and their children
        ids = []
        while only:
            currentid = only.pop()
            ids.append(currentid)
            if currentid in additions.keys():
                only.extend(additions[currentid])
        products = [ifcfile[currentid] for currentid in ids]

    if DEBUG: print "done."

    count = 0
    from FreeCAD import Base
    progressbar = Base.ProgressIndicator()
    progressbar.start("Importing IFC objects...",len(products))
    if DEBUG: print "Processing objects..."

    # products
    for product in products:

        pid = product.id()
        guid = product.GlobalId
        ptype = product.is_a()
        if DEBUG: print count+1,"/",len(products)," creating object #",pid," : ",ptype,
        name = str(ptype[3:])
        if product.Name:
            name = product.Name.encode("utf8")
        if PREFIX_NUMBERS: name = "ID" + str(pid) + " " + name
        obj = None
        baseobj = None
        brep = None
        shape = None

        archobj = True  # assume all objects not in structuralifcobjects are architecture
        structobj = False
        if ptype in structuralifcobjects:
            archobj = False
            structobj = True
            if DEBUG: print " (struct)",
        else:
            if DEBUG: print " (arch)",
        if MERGE_MODE_ARCH == 4 and archobj:
            if DEBUG: print " skipped."
            continue
        if MERGE_MODE_STRUCT == 3 and not archobj:
            if DEBUG: print " skipped."
            continue
        if pid in skip: # user given id skip list
            if DEBUG: print " skipped."
            continue
        if ptype in SKIP: # preferences-set type skip list
            if DEBUG: print " skipped."
            continue

        # detect if this object is sharing its shape
        clone = None
        store = None
        prepr = None
        try:
            prepr = product.Representation
        except:
            if DEBUG: print " ERROR unable to get object representation",
        if prepr and (MERGE_MODE_ARCH == 0) and archobj and CREATE_CLONES:
            for s in prepr.Representations:
                if s.RepresentationIdentifier.upper() == "BODY":
                    if s.Items[0].is_a("IfcMappedItem"):
                        bid = s.Items[0].MappingSource.id()
                        if bid in sharedobjects:
                            clone = sharedobjects[bid]
                        else:
                            sharedobjects[bid] = None
                            store = bid

        if structobj:
            settings.set(settings.INCLUDE_CURVES,True)
        else:
            settings.set(settings.INCLUDE_CURVES,False)
        try:
            cr = ifcopenshell.geom.create_shape(settings,product)
            brep = cr.geometry.brep_data
        except:
            pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care

        if brep:
            if DEBUG: print " ",str(len(brep)/1000),"k ",

            shape = Part.Shape()
            shape.importBrepFromString(brep)

            shape.scale(1000.0) # IfcOpenShell always outputs in meters

            if not shape.isNull():
                if (MERGE_MODE_ARCH > 0 and archobj) or structobj:
                    if ptype == "IfcSpace": # do not add spaces to compounds
                        if DEBUG: print "skipping space ",pid
                    elif structobj:
                        structshapes[pid] = shape
                        if DEBUG: print shape.Solids," ",
                        baseobj = shape
                    else:
                        shapes[pid] = shape
                        if DEBUG: print shape.Solids," ",
                        baseobj = shape
                else:
                    if clone:
                        if DEBUG: print "clone ",
                    else:
                        if GET_EXTRUSIONS:
                            ex = Arch.getExtrusionData(shape)
                            if ex:
                                print "extrusion ",
                                baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint")
                                # bug in ifcopenshell? Some faces of a shell may have non-null placement
                                # workaround to remove the bad placement: exporting/reimporting as step
                                if not ex[0].Placement.isNull():
                                    import tempfile
                                    fd, tf = tempfile.mkstemp(suffix=".stp")
                                    ex[0].exportStep(tf)
                                    f = Part.read(tf)
                                    os.close(fd)
                                    os.remove(tf)
                                else:
                                    f = ex[0]
                                baseface.Shape = f
                                baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body")
                                baseobj.Base = baseface
                                baseobj.Dir = ex[1]
                                if FreeCAD.GuiUp:
                                    baseface.ViewObject.hide()
                        if (not baseobj):
                            baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
                            baseobj.Shape = shape
            else:
                if DEBUG: print  "null shape ",
            if not shape.isValid():
                if DEBUG: print "invalid shape ",
                #continue

        else:
            if DEBUG: print " no brep ",

        if MERGE_MODE_ARCH == 0 and archobj:

            # full Arch objects
            for freecadtype,ifctypes in typesmap.items():
                if ptype in ifctypes:
                    if clone:
                        obj = getattr(Arch,"make"+freecadtype)(name=name)
                        obj.CloneOf = clone
                        if shape:
                            if shape.Solids:
                                s1 = shape.Solids[0]
                            else:
                                s1 = shape
                            if clone.Shape.Solids:
                                s2 = clone.Shape.Solids[0]
                            else:
                                s1 = clone.Shape
                            if hasattr(s1,"CenterOfMass") and hasattr(s2,"CenterOfMass"):
                                v = s1.CenterOfMass.sub(s2.CenterOfMass)
                                r = getRotation(product)
                                if not r.isNull():
                                    v = v.add(s2.CenterOfMass)
                                    v = v.add(r.multVec(s2.CenterOfMass.negative()))
                                obj.Placement.Rotation = r
                                obj.Placement.move(v)
                            else:
                                print "failed to compute placement ",
                    else:
                        obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
                        if store:
                            sharedobjects[store] = obj
                    obj.Label = name
                    if hasattr(obj,"Description") and hasattr(product,"Description"):
                        if product.Description:
                            obj.Description = product.Description
                    if FreeCAD.GuiUp and baseobj:
                        if hasattr(baseobj,"ViewObject"):
                            baseobj.ViewObject.hide()
                    # setting role
                    try:
                        r = ptype[3:]
                        tr = dict((v,k) for k, v in translationtable.iteritems())
                        if r in tr.keys():
                            r = tr[r]
                        # remove the "StandardCase"
                        if "StandardCase" in r:
                            r = r[:-12]
                        obj.Role = r
                    except:
                        pass
                    # setting uid
                    if hasattr(obj,"IfcAttributes"):
                        a = obj.IfcAttributes
                        a["IfcUID"] = str(guid)
                        obj.IfcAttributes = a
                    break
            if not obj:
                obj = Arch.makeComponent(baseobj,name=name)
            if obj:
                sols = str(obj.Shape.Solids) if hasattr(obj,"Shape") else ""
                if DEBUG: print sols
                objects[pid] = obj

        elif (MERGE_MODE_ARCH == 1 and archobj) or (MERGE_MODE_STRUCT == 0 and not archobj):

            # non-parametric Arch objects
            if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
                for freecadtype,ifctypes in typesmap.items():
                    if ptype in ifctypes:
                        obj = getattr(Arch,"make"+freecadtype)(baseobj=None,name=name)
            elif baseobj:
                obj = Arch.makeComponent(baseobj,name=name,delete=True)

        elif (MERGE_MODE_ARCH == 2 and archobj) or (MERGE_MODE_STRUCT == 1 and not archobj):

            # Part shapes
            if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
                for freecadtype,ifctypes in typesmap.items():
                    if ptype in ifctypes:
                        obj = getattr(Arch,"make"+freecadtype)(baseobj=None,name=name)
            elif baseobj:
                obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
                obj.Shape = shape

        if obj:

            obj.Label = name
            objects[pid] = obj

            # properties
            if pid in properties:
                if IMPORT_PROPERTIES and hasattr(obj,"IfcProperties") :
                    ifc_spreadsheet = Arch.makeIfcSpreadsheet()
                    n=2
                    for c in properties[pid].keys():
                        o = ifcfile[c]
                        if DEBUG : print("propertyset Name",o.Name,type(o.Name))
                        catname = o.Name
                        for p in properties[pid][c]:
                            l = ifcfile[p]
                            if l.is_a("IfcPropertySingleValue"):
                                if DEBUG :
                                    print("property name",l.Name,type(l.Name))
                                ifc_spreadsheet.set(str('A'+str(n)), catname.encode("utf8"))
                                ifc_spreadsheet.set(str('B'+str(n)), l.Name.encode("utf8"))
                                if l.NominalValue :
                                    if DEBUG :
                                        print("property NominalValue",l.NominalValue.is_a(),type(l.NominalValue.is_a()))
                                        print("property NominalValue.wrappedValue",l.NominalValue.wrappedValue,type(l.NominalValue.wrappedValue))
                                        #print("l.NominalValue.Unit",l.NominalValue.Unit,type(l.NominalValue.Unit))
                                    ifc_spreadsheet.set(str('C'+str(n)), l.NominalValue.is_a())
                                    if l.NominalValue.is_a() in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']:
                                        ifc_spreadsheet.set(str('D'+str(n)), "'" + str(l.NominalValue.wrappedValue.encode("utf8")))
                                    else :
                                        ifc_spreadsheet.set(str('D'+str(n)), str(l.NominalValue.wrappedValue))
                                    if hasattr(l.NominalValue,'Unit') :
                                        ifc_spreadsheet.set(str('E'+str(n)), str(l.NominalValue.Unit))
                                n += 1
                        obj.IfcProperties = ifc_spreadsheet
                elif hasattr(obj,"IfcAttributes"):
                    a = obj.IfcAttributes
                    for c in properties[pid].keys():
                        for p in properties[pid][c]:
                            l = ifcfile[p]
                            if l.is_a("IfcPropertySingleValue"):
                                a[l.Name.encode("utf8")] = str(l.NominalValue)
                    obj.IfcAttributes = a

            # color
            if FreeCAD.GuiUp and (pid in colors) and hasattr(obj.ViewObject,"ShapeColor"):
                if DEBUG: print "    setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255)
                obj.ViewObject.ShapeColor = colors[pid]

            # if DEBUG is on, recompute after each shape
            if DEBUG: FreeCAD.ActiveDocument.recompute()

        count += 1
        progressbar.next()

    progressbar.stop()
    FreeCAD.ActiveDocument.recompute()

    if MERGE_MODE_STRUCT == 2:

        if DEBUG: print "Joining Structural shapes..."

        for host,children in groups.items(): # Structural
            if ifcfile[host].is_a("IfcStructuralAnalysisModel"):
                compound = []
                for c in children:
                    if c in structshapes.keys():
                        compound.append(structshapes[c])
                        del structshapes[c]
                if compound:
                    name = ifcfile[host].Name or "AnalysisModel"
                    if PREFIX_NUMBERS: name = "ID" + str(host) + " " + name
                    obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
                    obj.Label = name
                    obj.Shape = Part.makeCompound(compound)
        if structshapes: # remaining Structural shapes
            obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedStruct")
            obj.Shape = Part.makeCompound(structshapes.values())
    else:

        if DEBUG: print "Processing Struct relationships..."

        # groups
        for host,children in groups.items():
            if ifcfile[host].is_a("IfcStructuralAnalysisModel"):
                # print host, ' --> ', children
                obj =  FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","AnalysisModel")
                objects[host] = obj
                if host in objects.keys():
                    cobs = []
                    childs_to_delete = []
                    for child in children:
                        if child in objects.keys():
                            cobs.append(objects[child])
                            childs_to_delete.append(child)
                    for c in childs_to_delete:
                        children.remove(c)  # to not process the child again in remaining groups
                    if cobs:
                        if DEBUG: print "adding ",len(cobs), " object(s) to ", objects[host].Label
                        Arch.addComponents(cobs,objects[host])
                        if DEBUG: FreeCAD.ActiveDocument.recompute()

        if MERGE_MODE_ARCH > 2:  # if ArchObj is compound or ArchObj not imported
            FreeCAD.ActiveDocument.recompute()

            # cleaning bad shapes
            for obj in objects.values():
                if obj.isDerivedFrom("Part::Feature"):
                    if obj.Shape.isNull():
                        Arch.rebuildArchShape(obj)

    # processing remaining (normal) groups
    for host,children in groups.items():
        if ifcfile[host].is_a("IfcGroup"):
            if ifcfile[host].Name:
                grp_name = ifcfile[host].Name
            else:
                if DEBUG: print "no group name specified for entity: #", ifcfile[host].id(), ", entity type is used!"
                grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id())
            grp =  FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name)
            objects[host] = grp
            for child in children:
                if child in objects.keys():
                    grp.addObject(objects[child])
                else:
                    if DEBUG: print "unable to add object: #", child, "  to group: #", ifcfile[host].id(), ", ", grp_name

    if MERGE_MODE_ARCH == 3:

        if DEBUG: print "Joining Arch shapes..."

        for host,children in additions.items(): # Arch
            if ifcfile[host].is_a("IfcBuildingStorey"):
                compound = []
                for c in children:
                    if c in shapes.keys():
                        compound.append(shapes[c])
                        del shapes[c]
                    if c in additions.keys():
                        for c2 in additions[c]:
                            if c2 in shapes.keys():
                                compound.append(shapes[c2])
                                del shapes[c2]
                if compound:
                    name = ifcfile[host].Name or "Floor"
                    if PREFIX_NUMBERS: name = "ID" + str(host) + " " + name
                    obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
                    obj.Label = name
                    obj.Shape = Part.makeCompound(compound)
        if shapes: # remaining Arch shapes
            obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedArch")
            obj.Shape = Part.makeCompound(shapes.values())

    else:

        if DEBUG: print "Processing Arch relationships..."

        # subtractions
        if SEPARATE_OPENINGS:
            for subtraction in subtractions:
                if (subtraction[0] in objects.keys()) and (subtraction[1] in objects.keys()):
                    if DEBUG: print "subtracting ",objects[subtraction[0]].Label, " from ", objects[subtraction[1]].Label
                    Arch.removeComponents(objects[subtraction[0]],objects[subtraction[1]])
                    if DEBUG: FreeCAD.ActiveDocument.recompute()

        # additions
        for host,children in additions.items():
            if host in objects.keys():
                cobs = [objects[child] for child in children if child in objects.keys()]
                if cobs:
                    if DEBUG and (len(cobs) > 10) and ( not(Draft.getType(objects[host]) in ["Site","Building","Floor"])):
                        # avoid huge fusions
                        print "more than 10 shapes to add: skipping."
                    else:
                        if DEBUG: print "adding ",len(cobs), " object(s) to ", objects[host].Label
                        Arch.addComponents(cobs,objects[host])
                        if DEBUG: FreeCAD.ActiveDocument.recompute()

        FreeCAD.ActiveDocument.recompute()

        # cleaning bad shapes
        for obj in objects.values():
            if obj.isDerivedFrom("Part::Feature"):
                if obj.Shape.isNull():
                    Arch.rebuildArchShape(obj)

    FreeCAD.ActiveDocument.recompute()

    # 2D elements

    if DEBUG and annotations: print "Creating 2D geometry..."

    for annotation in annotations:
        aid = annotation.id()
        if aid in skip: continue # user given id skip list
        if "IfcAnnotation" in SKIP: continue # preferences-set type skip list
        name = "Annotation"
        if annotation.Name:
            name = annotation.Name.encode("utf8")
        if PREFIX_NUMBERS: name = "ID" + str(aid) + " " + name
        shapes2d = []
        for repres in annotation.Representation.Representations:
            shapes2d.extend(setRepresentation(repres))
        if shapes2d:
            sh = Part.makeCompound(shapes2d)
            pc = str(int((float(count)/(len(products)+len(annotations))*100)))+"% "
            if DEBUG: print pc,"creating object ",aid," : Annotation with shape: ",sh
            o = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
            o.Shape = sh
        count += 1

    FreeCAD.ActiveDocument.recompute()

    # Materials

    if DEBUG and materials: print "Creating materials..."

    fcmats = {}
    for material in materials:
        name = "Material"
        if material.Name:
            name = material.Name.encode("utf8")
        if MERGE_MATERIALS and (name in fcmats.keys()):
            mat = fcmats[name]
        else:
            mat = Arch.makeMaterial(name=name)
            mdict = {}
            if material.id() in colors:
                mdict["Color"] = str(colors[material.id()])
            if mdict:
                mat.Material = mdict
            fcmats[name] = mat
        for o,m in mattable.items():
            if m == material.id():
                if o in objects:
                    if hasattr(objects[o],"BaseMaterial"):
                        objects[o].BaseMaterial = mat

    FreeCAD.ActiveDocument.recompute()

    if FreeCAD.GuiUp:
        import FreeCADGui
        FreeCADGui.SendMsgToActiveView("ViewFit")
    print "Finished importing."
    return doc


def export(exportList,filename):
    "exports FreeCAD contents to an IFC file"

    getPreferences()

    try:
        global ifcopenshell
        import ifcopenshell
    except:
        FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
        return

    version = FreeCAD.Version()
    owner = FreeCAD.ActiveDocument.CreatedBy
    email = ''
    if ("@" in owner) and ("<" in owner):
        s = owner.split("<")
        owner = s[0]
        email = s[1].strip(">")
    global template
    template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2])
    template = template.replace("$ifcschema",ifcopenshell.schema_identifier)
    template = template.replace("$owner",owner)
    template = template.replace("$company",FreeCAD.ActiveDocument.Company)
    template = template.replace("$email",email)
    template = template.replace("$now",str(int(time.time())))
    template = template.replace("$projectid",FreeCAD.ActiveDocument.Uid[:22].replace("-","_"))
    template = template.replace("$project",FreeCAD.ActiveDocument.Name)
    template = template.replace("$filename",filename)
    template = template.replace("$timestamp",str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())))
    templatefile = tempfile.mkstemp(suffix=".ifc")[1]
    of = pyopen(templatefile,"wb")
    of.write(template.encode("utf8"))
    of.close()
    global ifcfile, surfstyles, clones, sharedobjects
    ifcfile = ifcopenshell.open(templatefile)
    history = ifcfile.by_type("IfcOwnerHistory")[0]
    context = ifcfile.by_type("IfcGeometricRepresentationContext")[0]
    project = ifcfile.by_type("IfcProject")[0]
    objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True)
    objectslist = Arch.pruneIncluded(objectslist)
    products = {} # { Name: IfcEntity, ... }
    surfstyles = {} # { (r,g,b): IfcEntity, ... }
    clones = {} # { Basename:[Clonename1,Clonename2,...] }
    sharedobjects = {} # { BaseName: IfcRepresentationMap }
    count = 1
    groups = {} # { Host: [Child,Child,...] }

    # build clones table
    if CREATE_CLONES:
        for o in objectslist:
            b = Draft.getCloneBase(o,strict=True)
            if b:
                clones.setdefault(b.Name,[]).append(o.Name)

    #print "clones table: ",clones
    #print objectslist

    # testing if more than one site selected (forbidden in IFC)
    if len(Draft.getObjectsOfType(objectslist,"Site")) > 1:
        FreeCAD.Console.PrintError("More than one site is selected, which is forbidden by IFC standards. Please export only one site by IFC file.\n")
        return

    # products
    for obj in objectslist:

        # getting generic data
        name = str(obj.Label.encode("utf8"))
        description = str(obj.Description) if hasattr(obj,"Description") else ""

        # getting uid
        uid = None
        if hasattr(obj,"IfcAttributes"):
            if "IfcUID" in obj.IfcAttributes.keys():
                uid = str(obj.IfcAttributes["IfcUID"])
        if not uid:
            uid = ifcopenshell.guid.compress(uuid.uuid1().hex)
            # storing the uid for further use
            if STORE_UID and hasattr(obj,"IfcAttributes"):
                d = obj.IfcAttributes
                d["IfcUID"] = uid
                obj.IfcAttributes = d

        # setting the IFC type + name conversions
        if hasattr(obj,"Role"):
            ifctype = obj.Role.replace(" ","")
        else:
            ifctype = Draft.getType(obj)
        if ifctype in translationtable.keys():
            ifctype = translationtable[ifctype]
        ifctype = "Ifc" + ifctype
        if ifctype == "IfcGroup":
            groups[obj.Name] = [o.Name for o in obj.Group]
            continue
        ifctypes = []
        for v in typesmap.values():
            ifctypes.extend(v)
        if not ifctype in ifctypes:
            ifctype = "IfcBuildingElementProxy"

        # getting the "Force BREP" flag
        brepflag = False
        if hasattr(obj,"IfcAttributes"):
            if "FlagForceBrep" in obj.IfcAttributes.keys():
                if obj.IfcAttributes["FlagForceBrep"] == "True":
                    brepflag = True

        # getting the representation
        representation,placement,shapetype = getRepresentation(ifcfile,context,obj,forcebrep=(brepflag or FORCE_BREP))

        if DEBUG: print str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name

        # setting the arguments
        args = [uid,history,name,description,None,placement,representation,None]
        if ifctype in ["IfcSlab","IfcFooting","IfcRoof"]:
            args = args + ["NOTDEFINED"]
        elif ifctype in ["IfcWindow","IfcDoor"]:
            args = args + [obj.Width.Value/1000.0, obj.Height.Value/1000.0]
        elif ifctype == "IfcSpace":
            args = args + ["ELEMENT","INTERNAL",obj.Shape.BoundBox.ZMin/1000.0]
        elif ifctype == "IfcBuildingElementProxy":
            args = args + ["ELEMENT"]
        elif ifctype == "IfcSite":
            latitude = None
            longitude = None
            elevation = None
            landtitlenumber = None
            address = None
            args = args + ["ELEMENT",latitude,longitude,elevation,landtitlenumber,address]
        elif ifctype == "IfcBuilding":
            args = args + ["ELEMENT",None,None,None]
        elif ifctype == "IfcBuildingStorey":
            args = args + ["ELEMENT",obj.Placement.Base.z]

        # creating the product
        product = getattr(ifcfile,"create"+ifctype)(*args)
        products[obj.Name] = product

        # additions
        if hasattr(obj,"Additions") and (shapetype == "extrusion"):
            for o in obj.Additions:
                r2,p2,c2 = getRepresentation(ifcfile,context,o,forcebrep=True)
                if DEBUG: print "      adding ",c2," : ",o.Label
                prod2 = ifcfile.createIfcBuildingElementProxy(ifcopenshell.guid.compress(uuid.uuid1().hex),history,o.Label.encode("utf8"),None,None,p2,r2,None,"ELEMENT")
                ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Addition','',product,[prod2])

        # subtractions
        if hasattr(obj,"Subtractions") and (shapetype == "extrusion"):
            for o in obj.Subtractions:
                r2,p2,c2 = getRepresentation(ifcfile,context,o,forcebrep=True,subtraction=True)
                if DEBUG: print "      subtracting ",c2," : ",o.Label
                prod2 = ifcfile.createIfcOpeningElement(ifcopenshell.guid.compress(uuid.uuid1().hex),history,o.Label.encode("utf8"),None,None,p2,r2,None)
                ifcfile.createIfcRelVoidsElement(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Subtraction','',product,prod2)

        # properties
        ifcprop = False
        if hasattr(obj,"IfcProperties"):
            if obj.IfcProperties:
                ifcprop = True
                if DEBUG : print("      adding ifc properties")
                if obj.IfcProperties.TypeId == 'Spreadsheet::Sheet':
                    sheet = obj.IfcProperties
                    propertiesDic = {}
                    categories = []
                    n=2
                    cell = True
                    while cell == True :
                        if hasattr(sheet,'A'+str(n)):
                            cat = sheet.get('A'+str(n))
                            key = sheet.get('B'+str(n))
                            tp = sheet.get('C'+str(n))
                            if hasattr(sheet,'D'+str(n)):
                                val = sheet.get('D'+str(n))
                            else:
                                val = ''
                            if isinstance(key, unicode):
                                key = key.encode("utf8")
                            else :
                                key = str(key)
                            tp = tp.encode("utf8")
                            if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']:
                                val = val.encode("utf8")
                            elif tp == "IfcBoolean":
                                if val == 'True':
                                    val = True
                                else:
                                    val = False
                            elif tp == "IfcInteger":
                                val = int(val)
                            else:
                                val = float(val)
                            unit = None
                            #unit = sheet.get('E'+str(n))
                            if cat in categories :
                                propertiesDic[cat].append({"key":key,"tp":tp,"val":val,"unit":unit})
                            else:
                                propertiesDic[cat] = [{"key":key,"tp":tp,"val":val,"unit":unit}]
                                categories.append(cat)
                            n += 1
                        else:
                            cell = False
                    for cat in propertiesDic:
                        props = []
                        for prop in propertiesDic[cat] :
                            if DEBUG :
                                print("key",prop["key"],type(prop["key"]))
                                print("tp",prop["tp"],type(prop["tp"]))
                                print("val",prop["val"],type(prop["val"]))
                            props.append(ifcfile.createIfcPropertySingleValue(prop["key"],None,ifcfile.create_entity(prop["tp"],prop["val"]),None))
                        pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,cat,None,props)
                        ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
        if (not ifcprop) and hasattr(obj,"IfcAttributes"):
            if obj.IfcAttributes:
                ifcprop = True
                #if DEBUG : print("      adding ifc attributes")
                props = []
                for key in obj.IfcAttributes:
                    if not (key in ["IfcUID","FlagForceBrep"]):
                        r = obj.IfcAttributes[key].strip(")").split("(")
                        if len(r) == 1:
                            tp = "IfcText"
                            val = r[0]
                        else:
                            tp = r[0]
                            val = "(".join(r[1:])
                            val = val.strip("'")
                            val = val.strip('"')
                            if DEBUG: print "      property ",key," : ",val.encode("utf8"), " (", str(tp), ")"
                            if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']:
                                val = val.encode("utf8")
                            elif tp == "IfcBoolean":
                                if val == ".T.":
                                    val = True
                                else:
                                    val = False
                            elif tp == "IfcInteger":
                                val = int(val)
                            else:
                                val = float(val)
                        props.append(ifcfile.createIfcPropertySingleValue(str(key),None,ifcfile.create_entity(str(tp),val),None))
                if props:
                    pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'PropertySet',None,props)
                    ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
        if not ifcprop:
            #if DEBUG : print("no ifc properties to export")
            pass

        count += 1

    # relationships
    sites = []
    buildings = []
    floors = []
    treated = []
    for floor in Draft.getObjectsOfType(objectslist,"Floor"):
        objs = Draft.getGroupContents(floor,walls=True)
        objs = Arch.pruneIncluded(objs)
        children = []
        for c in objs:
            if c.Name in products.keys():
                if not (c.Name in treated):
                    children.append(products[c.Name])
                    treated.append(c.Name)
        f = products[floor.Name]
        if children:
            ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'StoreyLink','',children,f)
        floors.append(floor.Name)
    for building in Draft.getObjectsOfType(objectslist,"Building"):
        objs = Draft.getGroupContents(building,walls=True,addgroups=True)
        objs = Arch.pruneIncluded(objs)
        children = []
        childfloors = []
        for c in objs:
            if not (c.Name in treated):
                if c.Name != building.Name: # getGroupContents + addgroups will include the building itself
                    if c.Name in products.keys():
                        if Draft.getType(c) == "Floor":
                            childfloors.append(products[c.Name])
                            treated.append(c.Name)
                        elif not (c.Name in treated):
                            children.append(products[c.Name])
                            treated.append(c.Name)
        b = products[building.Name]
        if children:
            ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLink','',children,b)
        if childfloors:
            ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLink','',b,childfloors)
        buildings.append(b)
    for site in Draft.getObjectsOfType(objectslist,"Site"):
        objs = Draft.getGroupContents(site,walls=True,addgroups=True)
        objs = Arch.pruneIncluded(objs)
        children = []
        childbuildings = []
        for c in objs:
            if c.Name != site.Name: # getGroupContents + addgroups will include the building itself
                if c.Name in products.keys():
                    if not (c.Name in treated):
                        if Draft.getType(c) == "Building":
                            childbuildings.append(products[c.Name])
                            treated.append(c.Name)
        sites.append(products[site.Name])
    if not sites:
        if DEBUG: print "No site found. Adding default site"
        sites = [ifcfile.createIfcSite(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Site",'',None,None,None,None,"ELEMENT",None,None,None,None,None)]
    ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'ProjectLink','',project,sites)
    if not buildings:
        if DEBUG: print "No building found. Adding default building"
        buildings = [ifcfile.createIfcBuilding(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Building",'',None,None,None,None,"ELEMENT",None,None,None)]
    ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'SiteLink','',sites[0],buildings)
    untreated = []
    for k,v in products.items():
        if not(k in treated):
            if k != buildings[0].Name:
                if not(Draft.getType(FreeCAD.ActiveDocument.getObject(k)) in ["Site","Building","Floor"]):
                    untreated.append(v)
    if untreated:
        ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLinkUnassignedObjects','',untreated,buildings[0])

    # materials
    materials = {}
    for m in Arch.getDocumentMaterials():
        relobjs = []
        for o in m.InList:
            if hasattr(o,"BaseMaterial"):
                if o.BaseMaterial:
                    if o.BaseMaterial.Name == m.Name:
                        if o.Name in products:
                            relobjs.append(products[o.Name])
        if relobjs:
            mat = ifcfile.createIfcMaterial(m.Label.encode("utf8"))
            materials[m.Label] = mat
            rgb = None
            for colorslot in ["Color","DiffuseColor","ViewColor"]:
                if colorslot in m.Material:
                    if m.Material[colorslot]:
                        if m.Material[colorslot][0] == "(":
                            rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")])
                            break
            if rgb:
                col = ifcfile.createIfcColourRgb(None,rgb[0],rgb[1],rgb[2])
                ssr = ifcfile.createIfcSurfaceStyleRendering(col,None,None,None,None,None,None,None,"FLAT")
                iss = ifcfile.createIfcSurfaceStyle(None,"BOTH",[ssr])
                psa = ifcfile.createIfcPresentationStyleAssignment([iss])
                isi = ifcfile.createIfcStyledItem(None,[psa],None)
                isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi])
                imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat)
            ifcfile.createIfcRelAssociatesMaterial(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'MaterialLink','',relobjs,mat)

    # groups
    sortedgroups = []
    while groups:
        for g in groups.keys():
            okay = True
            for c in groups[g]:
                if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) == "Group":
                    okay = False
                    for s in sortedgroups:
                        if s[0] == c:
                            okay = True
            if okay:
                sortedgroups.append([g,groups[g]])
        for g in sortedgroups:
            if g[0] in groups.keys():
                del groups[g[0]]
    #print "sorted groups:",sortedgroups
    for g in sortedgroups:
        if g[1]:
            children = []
            for o in g[1]:
                if o in products.keys():
                    children.append(products[o])
            if children:
                name = str(FreeCAD.ActiveDocument.getObject(g[0]).Label.encode("utf8"))
                grp = ifcfile.createIfcGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,name,'',None)
                products[g[0]] = grp
                ass = ifcfile.createIfcRelAssignsToGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupLink','',children,None,grp)


    if DEBUG: print "writing ",filename,"..."

    filename = decode(filename)

    ifcfile.write(filename)

    if STORE_UID:
        # some properties might have been changed
        FreeCAD.ActiveDocument.recompute()


def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1):
    """returns an IfcShapeRepresentation object or None"""

    import Part,math,DraftGeomUtils,DraftVecUtils
    shapes = []
    placement = None
    productdef = None
    shapetype = "no shape"
    tostore = False

    # check for clones
    if not subtraction:
        for k,v in clones.items():
            if (obj.Name == k ) or (obj.Name in v):
                if k in sharedobjects:
                    # base shape already exists
                    repmap = sharedobjects[k]
                    pla = obj.Placement
                    axis1 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
                    axis2 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
                    axis3 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1))))
                    origin = ifcfile.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))
                    transf = ifcfile.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3)
                    mapitem = ifcfile.createIfcMappedItem(repmap,transf)
                    shapes = [mapitem]
                    solidType = "MappedRepresentation"
                    shapetype = "clone"
                else:
                    # base shape not yet created
                    tostore = k

    if (not shapes) and (not forcebrep):
        profile = None
        if hasattr(obj,"Proxy"):
            if hasattr(obj.Proxy,"getProfiles"):
                p = obj.Proxy.getProfiles(obj,noplacement=True)
                extrusionv = obj.Proxy.getExtrusionVector(obj,noplacement=True)
                if not DraftVecUtils.isNull(extrusionv):
                    extrusionv.multiply(0.001) # to meters
                    if (len(p) == 1) and extrusionv:
                        p = p[0].copy()
                        p.scale(0.001) # to meters
                        r = obj.Proxy.getPlacement(obj)
                        r.Base = r.Base.multiply(0.001) # to meters
                        d = DraftGeomUtils.getNormal(p.Wires[0])
                        if r.isNull() and  ( (p.CenterOfMass.z > 0.001) or ( (d.getAngle(FreeCAD.Vector(0,0,1)) > 0.001) and (d.getAngle(FreeCAD.Vector(0,0,1)) < 3.14159) ) ):
                            # the object placement is null, but the profile is not in the XY plane.
                            npla = FreeCAD.Placement()
                            npla.Base = p.Vertexes[0].Point
                            nrot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),d)
                            npla.Rotation = nrot
                            r = npla
                            # p.Placement = p.Placement.multiply(npla.inverse()) # move the profile to origin - not working??
                            p.translate(p.Vertexes[0].Point.negative())
                            p.rotate(FreeCAD.Vector(0,0,0),nrot.inverted().Axis,math.degrees(nrot.inverted().Angle))
                            extrusionv = nrot.inverted().multVec(extrusionv) # move the extrusion vector to Z axis, mandatory in IFC

                        if len(p.Edges) == 1:

                            pxvc = ifcfile.createIfcDirection((1.0,0.0))
                            povc = ifcfile.createIfcCartesianPoint((0.0,0.0))
                            pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc)

                            # extruded circle
                            if isinstance(p.Edges[0].Curve,Part.Circle):
                                profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt, p.Edges[0].Curve.Radius)

                            # extruded ellipse
                            elif isinstance(p.Edges[0].Curve,Part.Ellipse):
                                profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt, p.Edges[0].Curve.MajorRadius, p.Edges[0].Curve.MinorRadius)

                        else:
                            curves = False
                            for e in p.Edges:
                                if isinstance(e.Curve,Part.Circle):
                                    curves = True

                            # extruded polyline
                            if not curves:
                                w = Part.Wire(Part.__sortEdges__(p.Edges))
                                pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]]
                                pol = ifcfile.createIfcPolyline(pts)

                            # extruded composite curve
                            else:
                                segments = []
                                last = None
                                edges = Part.__sortEdges__(p.Edges)
                                for e in edges:
                                    if isinstance(e.Curve,Part.Circle):
                                        follow = True
                                        if last:
                                            if not DraftVecUtils.equals(last,e.Vertexes[0].Point):
                                                follow = False
                                                last = e.Vertexes[0].Point
                                            else:
                                                last = e.Vertexes[-1].Point
                                        else:
                                            last = e.Vertexes[-1].Point
                                        p1 = math.degrees(-DraftVecUtils.angle(e.Vertexes[0].Point.sub(e.Curve.Center)))
                                        p2 = math.degrees(-DraftVecUtils.angle(e.Vertexes[-1].Point.sub(e.Curve.Center)))
                                        da = DraftVecUtils.angle(e.valueAt(e.FirstParameter+0.1).sub(e.Curve.Center),e.Vertexes[0].Point.sub(e.Curve.Center))
                                        if p1 < 0:
                                            p1 = 360 + p1
                                        if p2 < 0:
                                            p2 = 360 + p2
                                        if da > 0:
                                            follow = not(follow)
                                        xvc =       ifcfile.createIfcDirection((1.0,0.0))
                                        ovc =       ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center)[:2])
                                        plc =       ifcfile.createIfcAxis2Placement2D(ovc,xvc)
                                        cir =       ifcfile.createIfcCircle(plc,e.Curve.Radius)
                                        curve =     ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER")

                                    else:
                                        verts = [vertex.Point for vertex in e.Vertexes]
                                        if last:
                                            if not DraftVecUtils.equals(last,verts[0]):
                                                verts.reverse()
                                                last = e.Vertexes[0].Point
                                            else:
                                                last = e.Vertexes[-1].Point
                                        else:
                                            last = e.Vertexes[-1].Point
                                        pts =     [ifcfile.createIfcCartesianPoint(tuple(v)[:2]) for v in verts]
                                        curve =   ifcfile.createIfcPolyline(pts)
                                    segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve)
                                    segments.append(segment)

                                pol = ifcfile.createIfcCompositeCurve(segments,False)
                            profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol)

        if profile and not(DraftVecUtils.isNull(extrusionv)):
            xvc =       ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(1,0,0))))
            zvc =       ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(0,0,1))))
            ovc =       ifcfile.createIfcCartesianPoint(tuple(r.Base))
            lpl =       ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
            edir =      ifcfile.createIfcDirection(tuple(FreeCAD.Vector(extrusionv).normalize()))
            shape =     ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,extrusionv.Length)
            shapes.append(shape)
            solidType = "SweptSolid"
            shapetype = "extrusion"

    if not shapes:
        # brep representation
        fcshape = None
        solidType = "Brep"
        if subtraction:
            if hasattr(obj,"Proxy"):
                if hasattr(obj.Proxy,"getSubVolume"):
                    fcshape = obj.Proxy.getSubVolume(obj)
        if not fcshape:
            if hasattr(obj,"Shape"):
                if obj.Shape:
                    if not obj.Shape.isNull():
                        fcshape = obj.Shape
            elif hasattr(obj,"Terrain"):
                if obj.Terrain:
                    if hasattr(obj.Terrain,"Shape"):
                        if obj.Terrain.Shape:
                            if not obj.Terrain.Shape.isNull():
                                    fcshape = obj.Terrain.Shape
        if fcshape:
            solids = []
            if fcshape.Solids:
                dataset = fcshape.Solids
            else:
                dataset = fcshape.Shells
                if DEBUG: print "Warning! object contains no solids"

            # if this is a clone, place back the shapes in null position
            if tostore:
                for shape in dataset:
                    shape.Placement = FreeCAD.Placement()

            # new ifcopenshell serializer
            from ifcopenshell import geom
            if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE:
                p = geom.serialise(obj.Shape.exportBrepToString())
                productdef = ifcfile.add(p)
                for rep in productdef.Representations:
                    rep.ContextOfItems = context
                xvc = ifcfile.createIfcDirection((1.0,0.0,0.0))
                zvc = ifcfile.createIfcDirection((0.0,0.0,1.0))
                ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0))
                gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
                placement = ifcfile.createIfcLocalPlacement(None,gpl)
                shapetype = "advancedbrep"
                shapes = None
            else:
                # old method
                for fcsolid in dataset:
                    fcsolid.scale(0.001) # to meters
                    faces = []
                    curves = False
                    shapetype = "brep"
                    for fcface in fcsolid.Faces:
                        for e in fcface.Edges:
                            if DraftGeomUtils.geomType(e) != "Line":
                                try:
                                    if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001:
                                        curves = True
                                        break
                                except Part.OCCError:
                                    pass
                    if curves:
                        joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False)
                        usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False)
                        if not joinfacets:
                            shapetype = "triangulated"
                            if usedae:
                                import importDAE
                                tris = importDAE.triangulate(fcsolid)
                            else:
                                tris = fcsolid.tessellate(tessellation)
                            for tri in tris[1]:
                                pts =   [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri]
                                loop =  ifcfile.createIfcPolyLoop(pts)
                                bound = ifcfile.createIfcFaceOuterBound(loop,True)
                                face =  ifcfile.createIfcFace([bound])
                                faces.append(face)
                                fcsolid = Part.Shape() # empty shape so below code is not executed
                        else:
                            fcsolid = Arch.removeCurves(fcsolid,dae=usedae)
                            if not fcsolid:
                                if DEBUG: print "Error: Unable to triangulate shape"
                                fcsolid = Part.Shape()

                    for fcface in fcsolid.Faces:
                        loops = []
                        verts = [v.Point for v in Part.Wire(Part.__sortEdges__(fcface.OuterWire.Edges)).Vertexes]
                        c = fcface.CenterOfMass
                        v1 = verts[0].sub(c)
                        v2 = verts[1].sub(c)
                        n = fcface.normalAt(0,0)
                        if DraftVecUtils.angle(v2,v1,n) >= 0:
                            verts.reverse() # inverting verts order if the direction is couterclockwise
                        pts =   [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts]
                        loop =  ifcfile.createIfcPolyLoop(pts)
                        bound = ifcfile.createIfcFaceOuterBound(loop,True)
                        loops.append(bound)
                        for wire in fcface.Wires:
                            if wire.hashCode() != fcface.OuterWire.hashCode():
                                verts = [v.Point for v in Part.Wire(Part.__sortEdges__(wire.Edges)).Vertexes]
                                v1 = verts[0].sub(c)
                                v2 = verts[1].sub(c)
                                if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0:
                                    verts.reverse()
                                pts =   [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts]
                                loop =  ifcfile.createIfcPolyLoop(pts)
                                bound = ifcfile.createIfcFaceBound(loop,True)
                                loops.append(bound)
                        face =  ifcfile.createIfcFace(loops)
                        faces.append(face)

                    if faces:
                        shell = ifcfile.createIfcClosedShell(faces)
                        shape = ifcfile.createIfcFacetedBrep(shell)
                        shapes.append(shape)

    if shapes:

        if tostore:
            subrep = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
            xvc = ifcfile.createIfcDirection((1.0,0.0,0.0))
            zvc = ifcfile.createIfcDirection((0.0,0.0,1.0))
            ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0))
            gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
            repmap = ifcfile.createIfcRepresentationMap(gpl,subrep)
            pla = FreeCAD.ActiveDocument.getObject(tostore).Placement
            axis1 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
            axis2 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
            origin = ifcfile.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))
            axis3 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1))))
            transf = ifcfile.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3)
            mapitem = ifcfile.createIfcMappedItem(repmap,transf)
            shapes = [mapitem]
            sharedobjects[tostore] = repmap
            solidType = "MappedRepresentation"

        # set surface style
        if FreeCAD.GuiUp and (not subtraction) and hasattr(obj.ViewObject,"ShapeColor"):
            # only set a surface style if the object has no material.
            # apparently not needed, no harm in having both.
            #m = False
            #if hasattr(obj,"BaseMaterial"):
            #    if obj.BaseMaterial:
            #        if "Color" in obj.BaseMaterial.Material:
            #            m = True
            #if not m:
            rgb = obj.ViewObject.ShapeColor[:3]
            if rgb in surfstyles:
                psa = surfstyles[rgb]
            else:
                col = ifcfile.createIfcColourRgb(None,rgb[0],rgb[1],rgb[2])
                ssr = ifcfile.createIfcSurfaceStyleRendering(col,None,None,None,None,None,None,None,"FLAT")
                iss = ifcfile.createIfcSurfaceStyle(None,"BOTH",[ssr])
                psa = ifcfile.createIfcPresentationStyleAssignment([iss])
                surfstyles[rgb] = psa
            for shape in shapes:
                isi = ifcfile.createIfcStyledItem(shape,[psa],None)

        xvc = ifcfile.createIfcDirection((1.0,0.0,0.0))
        zvc = ifcfile.createIfcDirection((0.0,0.0,1.0))
        ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0))
        gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
        placement = ifcfile.createIfcLocalPlacement(None,gpl)
        representation = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
        productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation])

    return productdef,placement,shapetype


def setRepresentation(representation):
    """Returns a shape from a 2D IfcShapeRepresentation"""

    def getPolyline(ent):
        pts = []
        for p in ent.Points:
            c = p.Coordinates
            pts.append(FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0))
        return Part.makePolygon(pts)

    def getCircle(ent):
        c = ent.Position.Location.Coordinates
        c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
        r = ent.Radius
        return Part.makeCircle(r,c)

    result = []
    if representation.is_a("IfcShapeRepresentation"):
        for item in representation.Items:
            if item.is_a("IfcGeometricCurveSet"):
                for el in item.Elements:
                    if el.is_a("IfcPolyline"):
                        result.append(getPolyline(el))
                    elif el.is_a("IfcCircle"):
                        result.append(getCircle(el))
                    elif el.is_a("IfcTrimmedCurve"):
                        base = el.BasisCurve
                        t1 = el.Trim1[0].wrappedValue
                        t2 = el.Trim2[0].wrappedValue
                        if not el.SenseAgreement:
                            t1,t2 = t2,t1
                        if base.is_a("IfcPolyline"):
                            bc = getPolyline(base)
                            result.append(bc)
                        elif base.is_a("IfcCircle"):
                            bc = getCircle(base)
                            e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
                            d = base.Position.RefDirection.DirectionRatios
                            v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
                            a = -DraftVecUtils.angle(v)
                            e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
                            result.append(e)
    return result


def getRotation(entity):
    "returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation"
    try:
        rmap = entity.Representation.Representations[0].Items[0].MappingTarget
        u = FreeCAD.Vector(rmap.Axis1.DirectionRatios)
        v = FreeCAD.Vector(rmap.Axis2.DirectionRatios)
        w = FreeCAD.Vector(rmap.Axis3.DirectionRatios)
    except AttributeError:
        return FreeCAD.Rotation()
    import WorkingPlane
    p = WorkingPlane.plane(u=u,v=v,w=w)
    return p.getRotation().Rotation
